/*
 * CPU class represents the composition of all functional and interface units 
 * for the system.  It is a wrapper containing all aspects of the internal 
 * operational units of the system.
 * 
 * It is composed of a Control Unit, a Memory Unit, a Cache Unit, a Register File, 
 * an Address Unit, an Arithmetic Unit, a Shift/Rotate Unit, a Floating Point Unit,
 * a Vector Unit, a Device Unit and a Status Unit.
 * 
 * It also includes a Latency Register to control clock speed for the system.  Latency
 * determines how much the simulation will idle between instructions so that the 
 * simulation can either run at the fastest speed possible determined by the host system
 * or if the simulation should run at much slower speeds for user evaluation of the 
 * system operation.
 * 
 * The cpu encompases the main loop during which console input devices are queried
 * to update system state and if the system is not in a stop state, the next instruction
 * is fetched by the control unit, decoded and executed.   
 * */

package cpu.components;

import cpu.datatypes.Word;
import ui.Console;
import ui.Printer;
import cpu.datastores.*;

public class CPU {
	public AddressUnit addressunit;
	public ALU alu;
	public ControlUnit controlunit;
	public DeviceUnit deviceunit;
	public FPU fpu;
	public MemoryUnit memoryunit;
	public CacheUnit cacheunit;
	public RegisterFile registerfile;
	public SRU sru;
	public VU vu;
	public StatusUnit statusunit;
	
	public Register4 LATENCY;
	
	public int cycle = 0;

	public CPU( Console console, Printer printer ) {
		
		controlunit = new ControlUnit( this );
		memoryunit = new MemoryUnit( this );
		cacheunit = memoryunit.cacheunit;
		addressunit = new AddressUnit( this );
		sru = new SRU( this );
		alu = new ALU( this );
		deviceunit = new DeviceUnit( this, console, printer );
		fpu = new FPU( this );
		registerfile = new RegisterFile( this );
		vu = new VU( this );
		statusunit = new StatusUnit( this );
		
		LATENCY = new Register4( );
	}
	
	public void run( ) {
		// starting system boot process
		statusunit.BOOT.bit( 0 ).value = true;
		controlunit.updateBootIndicator( );
		deviceunit.boot( this );
		statusunit.BOOT.bit( 0 ).value = false;
		controlunit.updateBootIndicator( );
		
		//memoryunit.dump( );
		
		// system running
		statusunit.EXIT.bit( 0 ).value = false;
	
		// main program loop.
		while( statusunit.EXIT.bit( 0 ).value == false ) {
			// check the status of all the switches in case they have pending data.
			
			// check the status of the IPL button
				// if IPL status = 1 then was pressed
				// set PC to the load program
			deviceunit.DID.fromJavaInt( system.device.Identifiers.DEVICE_BTNIPL.ordinal( ) );
			deviceunit.status( );
			if( deviceunit.DSR.toJavaInt( ) != 0 ) {
				deviceunit.get( );
				if( deviceunit.DBR.toJavaInt( ) != 0 ) {
					
					memoryunit.MAR.fromSignedInteger( 20 );
					memoryunit.get( );
					controlunit.PC.fromSignedInteger( memoryunit.MBR.toSignedInteger( ) );

					deviceunit.DID.fromJavaInt( system.device.Identifiers.DEVICE_BTNSTARTSTOP.ordinal( ) );
					deviceunit.DBR.fromJavaInt( 0 );
					deviceunit.put( );
					
					statusunit.STOP.bit( 0 ).value = false;
				}
			}
				
			// check the status of the Start/Stop button
				// if start/stop status = 1 then its on otherwise its off
				// if on then set stop bit
				// if off then unset stop bit

			deviceunit.DID.fromJavaInt( system.device.Identifiers.DEVICE_BTNSTARTSTOP.ordinal( ) );
			deviceunit.status( );
			if( deviceunit.DSR.toJavaInt( ) != 0 ) {
				deviceunit.get( );
				if( deviceunit.DBR.toJavaInt( ) == 0 ) {
					statusunit.STOP.bit( 0 ).value = false;
				} else {
					statusunit.STOP.bit( 0 ).value = true;
				}
			}
			
			// check the status of the Step button
				// if step status = 1 then was pressed
				// set the step bit 
			
			deviceunit.DID.fromJavaInt( system.device.Identifiers.DEVICE_BTNSTEP.ordinal( ) );
			deviceunit.status( );
			if( deviceunit.DSR.toJavaInt( ) != 0 ) {
				deviceunit.get( );
				if( deviceunit.DBR.toJavaInt( ) == 0 ) {
					statusunit.STEP.bit( 0 ).value = false;
				} else {
					statusunit.STEP.bit( 0 ).value = true;
					statusunit.STOP.bit( 0 ).value = true;
					
					deviceunit.DID.fromJavaInt( system.device.Identifiers.DEVICE_BTNSTARTSTOP.ordinal( ) );
					deviceunit.DBR.fromJavaInt( 1 );
					deviceunit.put( );
				}
			}
			
			// check the status of the Commit button
			// if commit status = 1 then was pressed
				// then get the value of the register selector 
				// and get the value of the switch bank
				// put switch bank value to the correct register
			
			deviceunit.DID.fromJavaInt( system.device.Identifiers.DEVICE_BTNCOMMIT.ordinal( ) );
			deviceunit.status( );
			if( deviceunit.DSR.toJavaInt( ) != 0 ) {
				deviceunit.get( );
				if( deviceunit.DBR.toJavaInt( ) != 0 ) {
				
					deviceunit.DID.fromJavaInt( system.device.Identifiers.DEVICE_SWREGOVERRIDE.ordinal( ) );
					deviceunit.get( );
					Word switchbankvalue = deviceunit.DBR.toWord( );
					
					deviceunit.DID.fromJavaInt( system.device.Identifiers.DEVICE_SWREGOVERSELECT.ordinal( ) );
					deviceunit.get( );
					switch( deviceunit.DBR.toJavaInt( ) ) {
					case 0:	// UP = R0
						registerfile.R0.fromWord( switchbankvalue );
						break;
					case 1:	// UPRIGHT = R1
						registerfile.R1.fromWord( switchbankvalue );
						break;
					case 2:	// RIGHT = R2
						registerfile.R2.fromWord( switchbankvalue );
						break;
					case 3:	// DOWNRIGHT = R3
						registerfile.R3.fromWord( switchbankvalue );
						break;
					case 4:	// DOWN = IR
						controlunit.IR.fromWord( switchbankvalue );
						break;
					case 5:	// DOWNLEFT = PC
						controlunit.PC.fromWord( switchbankvalue );
						break;
					case 6:	// LEFT = MAR
						memoryunit.MAR.fromWord( switchbankvalue );
						break;
					case 7:	// UPLEFT = X0
						addressunit.X0.fromWord( switchbankvalue );
						break;
					}
				}
			}

			// check the status of the latency selector 0 = not changed; 1 - changed
				// if latency changed then get the value and set latency

			deviceunit.DID.fromJavaInt( system.device.Identifiers.DEVICE_SWLATENCYSELECT.ordinal( ) );
			deviceunit.status( );
			if( deviceunit.DSR.toJavaInt( ) != 0 ) {
				deviceunit.get( );
				LATENCY.fromJavaInt( deviceunit.DBR.toJavaInt( ) );
			}
			
			if( statusunit.STEP.bit( 0 ).value == true ) {
				statusunit.STOP.bit( 0 ).value = false;
			}
			
			controlunit.updateStopIndicator( );
			controlunit.updateRunIndicator( );
			
			idle( );
			
			if( statusunit.STOP.bit( 0 ).value == false ) {
				controlunit.next( );
			}
			if( statusunit.STEP.bit( 0 ).value == true ) {
				statusunit.STOP.bit( 0 ).value = true;
				statusunit.STEP.bit( 0 ).value = false;
			}
		}
	}
	
	public void startDebug() {
		controlunit.debug = true;
	}
	
	public void stopDebug() {
		controlunit.debug = false;
	}
	
	public void idle( ) {
		int latency = LATENCY.toJavaInt( );
		int interval = 0;
		if( latency > 0 ) {
			switch( latency ) {
			case 1:
				interval = 1;
				break;
			case 2:
				interval = 2;
				break;
			case 3:
				interval = 5;
				break;
			case 4:
				interval = 10;
				break;
			case 5:
				interval = 20;
				break;
			case 6:
				interval = 50;
				break;
			case 7:
				interval = 100;
				break;
			case 8:
				interval = 200;
				break;
			case 9:
				interval = 500;
				break;
			case 10:
				interval = 1000;
				break;
			}
			try {
				Thread.sleep( interval );
			} catch(Exception ex) {
				
			}
		}
	}
}
